from decimal import Decimal

import logging
from django.db import transaction
from django.utils import timezone
from django_redis import get_redis_connection
from rest_framework import serializers

from goods.models import SKU
from orders.models import OrderInfo, OrderGoods

logger = logging.getLogger("django")


class CartSKUSerializer(serializers.ModelSerializer):
    """
    购物车商品数据序列化器
    """
    count = serializers.IntegerField(label='数量')  # 此数量为已选商品的数量

    class Meta:
        model = SKU
        fields = ('id', 'name', 'default_image_url', 'price', 'count')


class OrderSettlementSerializer(serializers.Serializer):
    """
    订单结算数据序列化器
    """
    freight = serializers.DecimalField(label='运费', max_digits=10, decimal_places=2)
    skus = CartSKUSerializer(many=True)  # 序列化器的嵌套


class SaveOrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrderInfo
        fields = ['order_id', 'address', 'pay_method']
        # 只读字段列表
        read_only_fields = ["order_id"]
        extra_kwargs = {
            'address': {
                'write_only': True,
                'required': True,
            },
            'pay_method': {
                'write_only': True,
                'required': True
            }
        }

    def create(self, validated_data):
        """
        功能: 保存订单的信息
        思路：　１获取用户的基本信息，然后生成自定义的订单id, 保存订单的基本信息至数据库OrderInfo
               2 从redis中获取购买商品的所朋友数据，并且保存值数据库OrderGoods中,
               　　根据所有商品的数量　价格等数据，更新OrderInfo数据库中的所有的数据
               3 所有的数据操作完成后，　需要删除redis中的数据
               数据库操作注意：１　采用数据库事务　　2　采用乐观锁　解决并发的问题
        """
        # 1 获取当前下单的用户
        user = self.context["request"].user
        # 2 自定义的订单ID的生成
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ("%09d" % user.id)
        # 3 保存订单基本的数据信息　OrderInfo
        address = validated_data["address"]
        pay_method = validated_data["pay_method"]

        # transaction.atommic() 表示数据库设置成一个事务处理
        # 　数据库的事务只是针对mysql的操作，对于序列化器和django　或者redis的操作是没有影响的
        with transaction.atomic():
            # 设置事务保存点，在操作失败的时候或者校验失败的时候，直接回滚到这里
            save_id = transaction.savepoint()
            # 4 从redis中提取相应的数据
            try:
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,  # 订单的数量暂时为0
                    total_amount=Decimal(0),  # 订单的总额暂时为0
                    freight=Decimal(10),  # 邮费暂时定义为10
                    pay_method=pay_method,
                    status=OrderInfo.ORDER_STATUS_ENUM["UNSEND"] if pay_method == OrderInfo.PAY_METHODS_ENUM[
                        "CASH"] else OrderInfo.ORDER_STATUS_ENUM["UNPAID"],
                )

                # 获取购物车的信息
                redis_conn = get_redis_connection("cart")
                redis_cart = redis_conn.hgetall("cart_%s" % user.id)
                cart_selected = redis_conn.smembers('cart_selected_%s' % user.id)
                # 获取购物车所有勾选状态为True的商品信息
                # 将bytes类型转换为int类型
                cart = {}
                for sku_id in cart_selected:
                    cart[int(sku_id)] = int(redis_cart[sku_id])

                # 5. 遍历结算商品：
                for sku_id in cart:
                    while (True):
                        # 获取当前sku_id对应的商品信息
                        sku = SKU.objects.get(pk=sku_id)
                        # 本次购买的商品sku的数量
                        sku_count = cart[sku.id]

                        # 判断库存,防止购买的数量大于商品的库存
                        origin_stock = sku.stock  # 原始库存[这里使用乐观锁需要先保存原始的数据，配置mysql数据库]
                        origin_sales = sku.sales  # 原始销量
                        # 5.1 判断商品库存是否充足
                        if sku_count > origin_stock:
                            # 库存不足，把sql操作回滚到with语句的开端
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('商品库存不足')

                        # 用于演示并发下单
                        # import time
                        # time.sleep(5)

                        # 5.2 减少商品库存，增加商品销量[sku单品销量、SPU总销量]
                        # sku.stock = origin_stock - sku_count # 新库存
                        # sku.sales = origin_sales + sku_count # 新销量
                        # sku.save()

                        # 调整代码，使用乐观锁
                        new_stock = origin_stock - sku_count  # 新库存
                        new_sales = origin_sales + sku_count  # 新销量
                        # 使用乐观锁的步骤：
                        # 1. 先记录在查询数据时，　保存原始数据值
                        # 2. 在更新的时候，把原始数据数据值(库存)作为更新的条件
                        ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)

                        # 在乐观锁中并没有保存成功，那么就让用户重新判断商品库存
                        if ret == 0:
                            # 跳过本次循环
                            continue

                        # 如果ret的值不是0，则表示当前sku上的库存是充足的，也就是更新成功则执行后续代码
                        break

                    # 累计商品的SPU销量信息(sku.goods为SKU的信息)
                    sku.goods.sales += sku_count
                    sku.goods.save()

                    # 累计订单基本信息的数据(总的信息)
                    order.total_count += sku_count  # 累计本次订单的商品数量
                    order.total_amount += (sku.price * sku_count)  # 累计本次订单的总金额

                    # 5.3  保存当前订单的商品数据
                    OrderGoods.objects.create(
                        order=order,
                        sku=sku,
                        count=sku_count,
                        price=sku.price,
                    )
                # 包含邮费的商品的信息
                order.total_amount += order.freight
                order.save()

            except serializers.ValidationError:
                # 这里表示是序列化器，而校验错误抛出的异常是要给视图处理，返回给前端
                # 所以这里，我们这里不做任何处理，直接使用 raise　可以把当前错误往视图抛出
                raise
            except Exception as e:
                logger.error(e)
                # 如果出现别的异常，也要回滚视图，取消with语句中的sql操作
                transaction.savepoint_rollback(save_id)
                raise

            # 提交事务!!
            transaction.savepoint_commit(save_id)

            # 6. 在redis购物车中删除已计算商品数据
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, *cart_selected)
            pl.srem('cart_selected_%s' % user.id, *cart_selected)
            pl.execute()

            return order
